package com.atguigu.yygh.hosp.service.impl;
import com.atguigu.yygh.common.result.R;
import com.atguigu.yygh.hosp.dao.DepartmentRepository;
import com.atguigu.yygh.hosp.dao.HospitalRepository;
import com.atguigu.yygh.hosp.dao.ScheduleRepository;
import com.atguigu.yygh.hosp.service.ScheduleService;
import com.atguigu.yygh.model.hosp.BookingRule;
import com.atguigu.yygh.model.hosp.Department;
import com.atguigu.yygh.model.hosp.Hospital;
import com.atguigu.yygh.model.hosp.Schedule;
import com.atguigu.yygh.vo.hosp.BookingScheduleRuleVo;
import com.atguigu.yygh.vo.hosp.ScheduleOrderVo;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.joda.time.DateTime;
import org.joda.time.DateTimeConstants;
import org.joda.time.format.DateTimeFormat;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

@Service
public class ScheduleServiceImpl implements ScheduleService {

    @Autowired
    private ScheduleRepository scheduleRepository; //适用于查询条件的操作
    @Autowired
    private MongoTemplate mongoTemplate; //适用于聚合条件的操作
    @Autowired
    private HospitalRepository hospitalRepository; //查询hosptial表中的数据
    @Autowired
    private DepartmentRepository departmentRepository;//查询Department表中的数据

    //排班信息
    @Override
    public void saveSchedule(Schedule schedule) {
        //获取一个排班信息
        Schedule mongoSchedule = scheduleRepository.findByHoscodeAndDepcodeAndHosScheduleId(schedule.getHoscode(),
                schedule.getDepcode(),schedule.getHosScheduleId());

        //不存在就添加,否则修改
        if(mongoSchedule == null){
            schedule.setUpdateTime(new Date());
            schedule.setCreateTime(new Date());
            schedule.setIsDeleted(0);
        scheduleRepository.save(schedule);
        }else{
            schedule.setUpdateTime(new Date());
            schedule.setCreateTime(mongoSchedule.getCreateTime());
            schedule.setIsDeleted(mongoSchedule.getStatus());
            schedule.setId(mongoSchedule.getId());
            scheduleRepository.save(schedule);
        }
    }

    /*
    aggregate:表示设置聚合相关信息
    InputType:通过指定的pojo类找mongo中对应的集合
    OutputType:表示经过聚合之后的数据封装到什么对象中
     */
    @Override
    public Map<String, Object> getSchedulePage(Integer pageNum, Integer pageSize, String hoscode, String depcode) {
        //使用MongoDb聚合
        Aggregation aggregation = Aggregation.newAggregation(
                Aggregation.match(Criteria.where("hoscode").is(hoscode).and("depcode").is(depcode)), //查询医院编号为
                Aggregation.group("workDate")
                        .first("workDate").as("workDate") //分组
                        .sum("reservedNumber").as("reservedNumber") //对当前医生的可预约数求和
                        .sum("availableNumber").as("availableNumber"), //对当前医生的剩余可预约数求和
                        Aggregation.sort(Sort.by(Sort.Order.asc("workDate"))),//按照时间的升序排序
                        Aggregation.skip((pageNum-1)*pageSize),
                        Aggregation.limit(pageSize));

        AggregationResults<BookingScheduleRuleVo> aggregate = mongoTemplate.aggregate(aggregation, Schedule.class, BookingScheduleRuleVo.class);//将每一行封装放到BookingRule对象中得到一个聚合结果
        List<BookingScheduleRuleVo> mappedResults = aggregate.getMappedResults();//查到符合当前页面的聚合信息列表
        //将当前页面的聚合信息转换成星期
        for (BookingScheduleRuleVo mappedResult : mappedResults) {
            Date workDate = mappedResult.getWorkDate();//获取年月日时间
            String dayOfWeek = this.parseDateToDayOfWeek(workDate);
            //joda-time可以将年月日信息转成周几的方法
            mappedResult.setDayOfWeek(dayOfWeek);
        }

        //获取根据时间分组之后的总记录数
        Aggregation aggregation2 = Aggregation.newAggregation(
                Aggregation.match(Criteria.where("hoscode").is(hoscode).and("depcode").is(depcode)), //查询医院编号为
                Aggregation.group("workDate")
                        .first("workDate").as("workDate"));//分页

        AggregationResults<BookingScheduleRuleVo> aggregate2 = mongoTemplate.aggregate(aggregation2, Schedule.class, BookingScheduleRuleVo.class);//将每一行封装放到BookingRule对象中得到一个聚合结果
        List<BookingScheduleRuleVo> mappedResults1 = aggregate2.getMappedResults();

        //当前页面对应的列表信息和当前页面的总记录数
        Map<String, Object> resultMap = new HashMap<String, Object>();
        resultMap.put("total",mappedResults1.size());//总记录数
        resultMap.put("list",mappedResults);//当前页面对应的列表信息

        return resultMap;
    }

    //排班详情列表
    @Override
    public List<Schedule> getScheduleDetail(String hoscode, String depcode, String workDate) {
        return scheduleRepository.findByHoscodeAndDepcodeAndWorkDate(hoscode,depcode,new DateTime(workDate).toDate());
    }


    //查询医院科室排班信息
    @Override
    public Map<String, Object> getUserSchedulePage(Integer pageNum, Integer pageSize, String hoscode, String depcode) {
        //根据医院编号查询医院信息
        Hospital hospital = hospitalRepository.findByHoscode(hoscode);
        //获取bookingRule属性
        BookingRule bookingRule = hospital.getBookingRule();
        //凑当前页面连续的时间列表
        Page<Date> page = this.getDateList(pageNum,pageSize,bookingRule);
        List<Date> records = page.getRecords();//取出当前页连续时间列表

        //去mongo中的Schedule表中查询有么有当天排班信息
        Aggregation aggregation = Aggregation.newAggregation(
        Aggregation.match(Criteria.where("hoscode").is(hoscode).and("depcode").is(depcode).and("workDate").in(records)),
        //分组,求和,起别名,根据时间升序[排序
        Aggregation.group("workDate")
                .first("workDate").as("workDate")
                .sum("reservedNumber").as("reservedNumber")
                .sum("availableNumber").as("availableNumber"),
        Aggregation.sort(Sort.by(Sort.Order.asc("workDate")))
        );
        AggregationResults<BookingScheduleRuleVo> aggregate = mongoTemplate.aggregate(aggregation, Schedule.class, BookingScheduleRuleVo.class);
        List<BookingScheduleRuleVo> mappedResults = aggregate.getMappedResults();//得到每一天医生的总的可约数和剩余的可约数
         //将列表转换成前端需要的连续时间的map格式  //key:workDate  value:BookingScheduleRuleVo
        Map<Date, BookingScheduleRuleVo> collect = mappedResults.stream().collect(Collectors.toMap(BookingScheduleRuleVo::getWorkDate, BookingScheduleRuleVo -> BookingScheduleRuleVo));
        //创建一个list存放最终的数据
        List<BookingScheduleRuleVo> bookingScheduleRuleVoList = new ArrayList<BookingScheduleRuleVo>();
        //遍历连续的时间列表
        for (int i = 0, len = records.size();i < len; i++) {
            Date date = records.get(i);
            BookingScheduleRuleVo bookingScheduleRuleVo = collect.get(date);
            //对聚合信息进行判断,null表示没有改天的聚合信息,就返回给页面一个值表示,表示不可预约
            if(bookingScheduleRuleVo == null){
                bookingScheduleRuleVo = new BookingScheduleRuleVo();
                bookingScheduleRuleVo.setWorkDate(date);
                bookingScheduleRuleVo.setReservedNumber(0);//0表示正常
                bookingScheduleRuleVo.setAvailableNumber(-1); //-1表示无号
            }

            //将年月日转换成周几
            bookingScheduleRuleVo.setDayOfWeek(parseDateToDayOfWeek(date));
            //设置状态为正常
            bookingScheduleRuleVo.setStatus(0);
            //得到第一页第一条,此时此刻的时间是否超过了医院规定的挂号截止时间
            if(pageNum == 1 && i == 0){
                //得到今天的挂号截止时间
                DateTime dateTime = concatDateAndTime(new Date(), bookingRule.getStopTime());
                //如果挂号截止时间在此时此刻之前说明已经过了当天医院规定的挂号时间,将状态设置为-1
                if(dateTime.isBeforeNow()){
                bookingScheduleRuleVo.setStatus(-1);
                }
            }
            //最后一页最后一条判断
            if(pageNum == page.getPages() && i == (len-1)){
                //将状态设置为即将放号
                bookingScheduleRuleVo.setStatus(1);
            }
            //将聚合信息放到list
            bookingScheduleRuleVoList.add(bookingScheduleRuleVo);
        }

        Map<String, Object> resultMap = new HashMap<>();
        resultMap.put("items",bookingScheduleRuleVoList); //时间列表
        resultMap.put("total", page.getTotal()); //总页数
        resultMap.put("baseMap",page.getTotal());

        //其他基础数据
        Map<String, String> baseMap = new HashMap<>();
        //医院名称
        baseMap.put("hosname", hospital.getHosname());
        //科室
        Department department =departmentRepository.findByHoscodeAndDepcode(hoscode, depcode);
        //大科室名称
        baseMap.put("bigname", department.getBigname());
        //科室名称
        baseMap.put("depname", department.getDepname());
        //月
        baseMap.put("workDateString", new DateTime().toString("yyyy年MM月"));
        //放号时间
        baseMap.put("releaseTime", bookingRule.getReleaseTime());
        //停号时间
        baseMap.put("stopTime", bookingRule.getStopTime());
        resultMap.put("baseMap",baseMap);
        return resultMap; //返回给controller层
    }

    //根据排班id查询排班信息
    @Override
    public Schedule getScheduleById(String scheduleId) {
        Schedule schedule = null;
        Optional<Schedule> optional = scheduleRepository.findById(scheduleId);
        if(optional.isPresent()){
            schedule = optional.get();
            this.packageSchedule(schedule);
        }
        return schedule;
    }

    @Override
    public ScheduleOrderVo getRemoteScheduleById(String scheduleId) {
        Optional<Schedule> optional = scheduleRepository.findById(scheduleId);
        if(optional.isPresent()){
            Schedule schedule = optional.get();
            ScheduleOrderVo scheduleOrderVo = new ScheduleOrderVo();
            BeanUtils.copyProperties(schedule, scheduleOrderVo);

            Hospital hospital = hospitalRepository.findByHoscode(schedule.getHoscode());
            scheduleOrderVo.setHosname(hospital.getHosname());

            Department department = departmentRepository.findByHoscodeAndDepcode(schedule.getHoscode(), schedule.getDepcode());
            scheduleOrderVo.setDepname(department.getDepname());

            scheduleOrderVo.setReserveDate(schedule.getWorkDate());
            scheduleOrderVo.setReserveTime(schedule.getWorkTime());

            //计算退号截止时间
            BookingRule bookingRule = hospital.getBookingRule();
            Integer quitDay = bookingRule.getQuitDay();
            String quitTime = bookingRule.getQuitTime();
            DateTime dateTime = new DateTime(schedule.getWorkDate()).plusDays(quitDay);
            scheduleOrderVo.setQuitTime(concatDateAndTime(dateTime.toDate(), quitTime).toDate());

            //预约当前挂号截止时间
            scheduleOrderVo.setStopTime(concatDateAndTime(schedule.getWorkDate(),bookingRule.getStopTime()).toDate());
            return scheduleOrderVo;
        }
        return null;
    }

    //跟新剩余可预约数
    @Override
    public void updateAvailableNumber(String scheduleId, Integer availableNumber) {
        Optional<Schedule> optional = scheduleRepository.findById(scheduleId);
        if(optional.isPresent()){
            Schedule schedule = optional.get();
            schedule.setAvailableNumber(availableNumber);
            schedule.setUpdateTime(new Date()); //现在内存修改
            scheduleRepository.save(schedule); //持久化到mongo表
        }
    }

    private void packageSchedule(Schedule schedule) {
        String hoscode = schedule.getHoscode();//获取医院编号
        String depcode = schedule.getDepcode();//获取科室编号
        schedule.getParam().put("dayOfWeek", this.parseDateToDayOfWeek(schedule.getWorkDate()));//将时间转换成周几
        schedule.getParam().put("hosname",hospitalRepository.findByHoscode(hoscode).getHosname());//返回给前端医院编号
        schedule.getParam().put("depname",departmentRepository.findByHoscodeAndDepcode(hoscode,depcode).getDepname());//返回给前端科室信息
    }

    //将当前页面的时间列表转换成Date格式的样式
    private Page<Date> getDateList(Integer pageNum, Integer pageSize, BookingRule bookingRule) {
        Integer cycle = bookingRule.getCycle(); //取出医院周期//取出医院周期
        DateTime dateTime = concatDateAndTime(new Date(),bookingRule.getReleaseTime()); //取出医院规定挂号的起始时间

        //判断dateTime是否在此时此刻之前,如果是就说明此时此刻已经超过了当天医院规定的挂号时间,将当前的周期加1,表示为最后一天
        if(dateTime.isBeforeNow()){
            cycle = cycle + 1;
        }

        List<Date> datesList = new ArrayList<Date>();
        //将当前的周期每一天变成yyyy-MM-dd格式的时间,dateList中的到的是年月日11天的时间
        for (int i = 0; i < cycle; i++) {
            DateTime dateTime1 = new DateTime().plusDays(i);
            datesList.add(new DateTime(dateTime1.toString("yyyy-MM-dd")).toDate());//将得到的时间转成Date类型的格式存到集合中
        }
        //凑连续的当前页时间列表
        int start = (pageNum-1)*pageSize; //起始位置
        int end = start+pageSize; //结束位置

        //如果最后一页条数大于总条数,就把最后一页剩余总条数取出来给end
        if(end >datesList.size()){
            end=datesList.size();
        }

        List<Date> currentDateList = new ArrayList<Date>(); //创建当前时间列表
        //取出前七条数据
        for (int j = start; j < end; j++) {
            Date date = datesList.get(j);
            currentDateList.add(date);//每取一天的数据存进当前页的时间列表
        }

        //取出当前页7天的时间列表
        Page<Date> page = new Page<Date>(pageNum,pageSize,cycle);
        //设置当前页面对应的时间列表
        page.setRecords(currentDateList);
        return page;
    }

    //将时间转换成年月日时分秒的格式方法
    private DateTime concatDateAndTime(Date date, String releaseTime) {
        //将date转换成Sting类型
        String datetime = new DateTime(date).toString("yyyy-MM-dd") + " " + releaseTime;
        //将datetime类型的时间转换成datetime类型的时间方便计算
        return DateTimeFormat.forPattern("yyyy-MM-dd HH:mm").parseDateTime(datetime);
    }

    //将当前页面的聚合信息转换成星期方法的封装
    private String parseDateToDayOfWeek(Date workDate) {
        DateTime dateTime = new DateTime(workDate);

        //对传时间进行匹配转换成对应的星期
        String dayOfWeek="";
        switch(dateTime.getDayOfWeek()){
            case DateTimeConstants.SUNDAY:
                dayOfWeek="周日";
                break;
            case DateTimeConstants.SATURDAY:
                dayOfWeek="周六";
                break;case DateTimeConstants.FRIDAY:
                dayOfWeek="周周五";
                break;
            case DateTimeConstants.THURSDAY:
                dayOfWeek="周四";
                break;
            case DateTimeConstants.WEDNESDAY:
                dayOfWeek="周三";
                break;case DateTimeConstants.TUESDAY:
                dayOfWeek="周二";
                break;
            case DateTimeConstants.MONDAY:
                dayOfWeek="周一";
            default:
                break;
        }
        return dayOfWeek;
    }
}